/*
 * Copyright (C) 2015 Daniel Uppenkamp
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.googlecode.maurersmarbles;

import com.google.common.base.Preconditions;
import com.googlecode.maurersmarbles.drawables.CardDrawable;
import com.googlecode.maurersmarbles.drawables.Drawable;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 *
 * @author Daniel Uppenkamp
 */
public class CardsDisplay {

    /**
     * The scale size of the space between cards compared to the cards display
     * width.
     */
    protected static final float CARD_SPACE_SCALE_FACTOR = 0.015f;
    /**
     * The maximum number of cards that can be displayed at once.
     */
    protected static final int MAX_NUMBER_OF_CARDS = 5;
    /**
     * The lock that is used to control access to the card rectangles map.
     */
    protected static final Object CARD_RECTANGLES_LOCK = new Object();
    /**
     * Whether or not the display the cards face up.
     */
    protected boolean displayCardsFaceUp = true;
    /**
     * The height of a card.
     */
    protected double cardHeight;
    /**
     * The size of the space between cards.
     */
    protected double cardSpaceWidth;
    /**
     * The width of a card.
     */
    protected double cardWidth;
    /**
     * The map that contains the rectangles for each card.
     */
    protected Map<Card, Rectangle> cardRectangles = new HashMap();
    /**
     * The orientation of the cards.
     */
    protected Orientation orientation = Orientation.NORTH;
    /**
     * The rectangle to draw the cards in.
     */
    protected Rectangle outerRectangle;

    /**
     * Creates a new CardsDisplay.
     */
    public CardsDisplay() {
        // do nothing
    }

    /**
     * Creates a new CardsDisplay.
     *
     * @param displayCardsFaceUp whether or not the display the cards face up
     */
    public CardsDisplay(boolean displayCardsFaceUp) {
        this.displayCardsFaceUp = displayCardsFaceUp;
    }

    /**
     * Returns the card at the given screen x, y location, or null if there
     * isn't a card
     *
     * @param x the x coordinate on the screen
     * @param y the y coordinate on the screen
     * @return the card at the given screen x, y location, or null if there
     * isn't a card
     */
    public Card getCardAtScreenXY(int x, int y) {

        synchronized (CARD_RECTANGLES_LOCK) {

            for (Map.Entry<Card, Rectangle> entry : cardRectangles.entrySet()) {
                if (entry.getValue().contains(x, y)) {
                    return entry.getKey();
                }
            }

        }

        return null;

    }

    /**
     * Returns the card height.
     *
     * @return the card height
     */
    public double getCardHeight() {
        return cardHeight;
    }

    /**
     * Returns the card width.
     *
     * @return the card width
     */
    public double getCardWidth() {
        return cardWidth;
    }

    /**
     * Sets the card size. Note that calling setRectangle will calculate the the
     * card width and height based on the given rectangle. This function shall
     * be called after calling setRectangle to override the calculated values.
     *
     * @param width the card width
     * @param height the card height
     */
    public void setCardSize(int width, int height) {
        cardWidth = width;
        cardHeight = height;
    }

    /**
     * Returns a map that contains the drawables for the cards. The key into the
     * map is the Card and the value is the drawable.
     *
     * @param cards the list of cards to display
     * @return a map that contains the drawables for the cards
     */
    public Map<Object, Drawable> getDrawables(List<Card> cards) {

        synchronized (CARD_RECTANGLES_LOCK) {

            Map<Object, Drawable> drawables = new LinkedHashMap();

            // clear the previous card rectangles
            cardRectangles.clear();

            if (cards == null || cards.isEmpty()) {
                return drawables;
            }

            // TODO Sort the cards?
            double neededWidth = (cards.size() * cardWidth) + ((cards.size() - 1) * cardSpaceWidth);
            double cardX = 0;
            double cardY = 0;
            double width = cardWidth;
            double height = cardHeight;
            switch (orientation) {

                case NORTH:
                    cardX = outerRectangle.getX() + ((outerRectangle.getWidth() - neededWidth) / 2.0);
                    cardY = outerRectangle.getY();
                    break;

                case SOUTH:
                    cardX = outerRectangle.getX() + ((outerRectangle.getWidth() - neededWidth) / 2.0);
                    cardY = outerRectangle.getY() + (outerRectangle.getHeight() - cardHeight);
                    break;

                case EAST:
                    width = cardHeight;
                    height = cardWidth;
                    cardX = outerRectangle.getRight() - width;
                    cardY = outerRectangle.getY() + ((outerRectangle.getHeight() - neededWidth) / 2.0);
                    break;

                case WEST:
                    width = cardHeight;
                    height = cardWidth;
                    cardX = outerRectangle.getX();
                    cardY = outerRectangle.getY() + ((outerRectangle.getHeight() - neededWidth) / 2.0);
                    break;

            }

            for (Card card : cards) {

                // create the rectangle to draw this card in
                Rectangle rectangle = new Rectangle((int) cardX, (int) cardY, (int) width, (int) height);

                // save the rectangle so we can use it later to get a card from an x,y coordinate
                cardRectangles.put(card, rectangle);

                // create the drawable and add it to the map
                CardDrawable cardDrawable = new CardDrawable(displayCardsFaceUp ? card : null, rectangle);
                cardDrawable.setOrientation(orientation);
                drawables.put(card, cardDrawable);

                if (orientation == Orientation.NORTH || orientation == Orientation.SOUTH) {
                    cardX += cardWidth + cardSpaceWidth;
                }
                else {
                    cardY += cardWidth + cardSpaceWidth;
                }

            }

            return drawables;

        }

    }

    /**
     * Returns the orientation of the cards display.
     *
     * @return the orientation of the cards display
     */
    public Orientation getOrientation() {
        return orientation;
    }

    /**
     * Sets the orientation of the cards display.
     *
     * @param orientation the orientation of the cards display
     */
    public void setOrientation(Orientation orientation) {
        
        Preconditions.checkNotNull(orientation, "Null orientation");
        
        this.orientation = orientation;
        
    }

    /**
     * Returns the rectangle for the given card.
     *
     * @param card the card
     * @return the rectangle for the given card
     */
    public Rectangle getRectangleForCard(Card card) {

        synchronized (CARD_RECTANGLES_LOCK) {

            if (cardRectangles.containsKey(card)) {
                return cardRectangles.get(card);
            }

            // return a rectangle in the center of the display
            int startX = (int) (outerRectangle.getX() + ((outerRectangle.getWidth() - cardWidth) / 2.0));
            int startY = (int) (outerRectangle.getY());
            return new Rectangle(startX, startY, (int) cardWidth, (int) cardHeight);

        }

    }

    /**
     * Sets the rectangle to draw the cards in.
     *
     * @param rectangle the rectangle to draw the cards in
     */
    public void setRectangle(Rectangle rectangle) {

        Preconditions.checkNotNull(rectangle, "Null rectangle");
        
        this.outerRectangle = rectangle;

        /**
         * TODO Need to account for different screen sizes which include the 
         * height of the screen instead of just the width of the screen.
         */        
        cardSpaceWidth = CARD_SPACE_SCALE_FACTOR * rectangle.getWidth();
        cardWidth = (rectangle.getWidth() - (cardSpaceWidth * (MAX_NUMBER_OF_CARDS - 1))) / MAX_NUMBER_OF_CARDS;
        cardHeight = cardWidth / Card.WIDTH_TO_HEIGHT_RATIO;

    }

}
